﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Linq.Expressions;

namespace Sworm
{
    [System.Diagnostics.DebuggerDisplay("{TableName}, {ClassType}")]
    public sealed class TableData
    {
        public TableData()
        {
            OptionalData = new OptionalTableData();
        }

        public Type ClassType { get; internal set; }
        public SqlIdentifier Name { get; internal set; }
        public List<ColumnData> Columns { get; internal set; }
        public List<RelationshipData> Relationships { get; internal set; }

        public OptionalTableData OptionalData { get; private set; }

        public ColumnData TryFindColumn(string name)
        {
            return Columns.FirstOrDefault(c => c.Name.Unescaped.EqualsIgnoreCase(name));
        }

        public ColumnData RequireColumn(string name)
        {
            var column = TryFindColumn(name);
            if (column == null)
            {
                throw new ArgumentException(name + " is not a column on table " + Name);
            }
            return column;
        }

        public ColumnData RequireColumn<T, P>(Expression<Func<T, P>> expression)
        {
            if (!ClassType.IsAssignableFrom(typeof(T)))
            {
                throw new ArgumentException("expression is on an incompatible type");
            }

            // in case it's (x) => (object)(x.IntProp)
            var body = expression.Body;
            if (body is UnaryExpression)
            {
                body = ((UnaryExpression)body).Operand;
            }

            var m = body as MemberExpression;
            if (m == null || m.Member == null)
            {
                throw new ArgumentException("expected a member expression in the form x => x.Property");
            }

            var column = Columns.SingleOrDefault(c => c.Member.AsMemberInfo == m.Member);
            if (column == null)
            {
                throw new ArgumentException(m.Member.Name + " is not a column of the table");
            }
            return column;
        }

        public ColumnData RequirePrimaryKey()
        {
            var pk = OptionalData.PrimaryKey;
            if (pk == null)
            {
                throw new MissingPrimaryKeyException("A primary key is required for this"
                    + " operation, but none was found on " + this.ClassType.FullName);
            }
            return pk;
        }

        public T CreateInstance<T>()
        {
            return (T)(Activator.CreateInstance(ClassType));
        }
    }

    public sealed class OptionalTableData
    {
        public ColumnData PrimaryKey { get; internal set; }
        public bool HasAutoIncrementPK { get; internal set; }
    }

    [System.Diagnostics.DebuggerDisplay("{Debugger}")]
    public sealed class ColumnData
    {
        public ColumnData()
        {
            OptionalData = new OptionalColumnData();
        }

        public TableData Table { get; internal set; }
        public Member Member { get; internal set; }
        public SqlIdentifier Name { get; internal set; }
        public System.Data.DbType SqlType { get; internal set; }

        public OptionalColumnData OptionalData { get; private set; }

        private string Debugger
        {
            get
            {
                return string.Format("{0} on {1}", Member.Name, Table.ClassType);
            }
        }

        private void SetValue(object instance, object value)
        {
            if (value == DBNull.Value)
            {
                value = null;
            }
            if (value == null && Member.Type.IsValueType)
            {
                return;
            }
            Member.SetValue(instance, value);
        }

        public object GetValue(object instance)
        {
            return Member.GetValue(instance);
        }

        public void SetDbValue(object instance, DbAccessor accessor)
        {
            if (this.OptionalData.FromDbConverter != null)
            {
                var value = OptionalData.FromDbConverter(accessor);
                SetValue(instance, value);
            }
            else if (Member.TrySetDbResult(instance, accessor))
            {
                // do nothing
            }
            else
            {
                SetValue(instance, accessor.GetObject());
            }
        }

        internal object PrepareParam(object param)
        {
            if (OptionalData.ToDbConverter != null)
            {
                param = OptionalData.ToDbConverter(param);
            }

            return param ?? DBNull.Value;
        }
    }

    public sealed class OptionalColumnData
    {
        // this might be faster but too hard right now
        //public Action<DbAccessor, object> FromDbConverter { get; set; }

        public Func<DbAccessor, object> FromDbConverter { get; internal set; }
        public Func<object, object> ToDbConverter { get; internal set; }
    }

    [System.Diagnostics.DebuggerDisplay("{Debugger}")]
    public sealed class RelationshipData
    {
        public ColumnData SourceColumn { get; internal set; }
        public Member SourceMember { get; internal set; }
        public TableData SourceTable { get { return SourceColumn.Table; } }

        public ColumnData DestinationColumn { get; internal set; }
        public Member DestinationMember { get; internal set; }
        public TableData DestinationTable { get { return DestinationColumn.Table; } }

        public Type SourceType { get { return SourceColumn.Table.ClassType; } }
        public Type DestinationType { get { return DestinationColumn.Table.ClassType; } }

        private string Debugger
        {
            get
            {
                return string.Format("{0}.{1} to {2}.{3} - property: {4}",
                    SourceColumn.Table.Name.Unescaped, SourceColumn.Name.Unescaped,
                    DestinationColumn.Table.Name.Unescaped, DestinationColumn.Name.Unescaped,
                    SourceMember.Name);
            }
        }
    }
}
